﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#include "GLFrameBuffer.h"

namespace UX_STL
{
namespace GLShell
{
	
//-------------------------- GLFrameBuffer ------------------------//
/*
=================================================
	конструктор
=================================================
*/
	GLFrameBuffer::GLFrameBuffer(): _uID(0)
	{
		FOR( i, _aBuffers ) {
			_aBuffers[i].eAttach = _s_IndexToAttachment( i );
		}
	}

	GLFrameBuffer::GLFrameBuffer(GLFrameBuffer &cFB):
		_uID(cFB._uID), _aBuffers(cFB._aBuffers), _aDrawBuffers(cFB._aDrawBuffers)
	{
		cFB._uID = 0;
		cFB.Destroy();
	}

/*
=================================================
	деструктор
=================================================
*/
	GLFrameBuffer::~GLFrameBuffer()
	{
		if ( _uID != 0 )
		{
			if ( SMAN()->GetDrawFBO() == _uID )
				SMAN()->BindDrawFramebuffer( 0 );
			
			GLOM()->DeleteFramebuffers( 1, &_uID );
			_uID = 0;
		}
	}

/*
=================================================
	удаляет буферы
=================================================
*/
	void GLFrameBuffer::Destroy()
	{
		if ( _uID != 0 )
		{
			if ( SMAN()->GetDrawFBO() == _uID )
				SMAN()->BindDrawFramebuffer( 0 );
			
			GLOM()->DeleteFramebuffers( 1, &_uID );
			_uID = 0;
		}

		FOR( i, _aBuffers ) {
			_aBuffers[i].eTarget = gl_texture::_UNKNOWN;
			_aBuffers[i].uID	 = 0;
			_aDrawBuffers[i]	 = gl_fbo_attach::NONE;
		}
		_aDrawBuffers[ _DEPTH ]			= gl_fbo_attach::DEPTH;
		_aDrawBuffers[ _DEPTH_STENCIL ]	= gl_fbo_attach::DEPTH_STENCIL;
		_aDrawBuffers[ _STENCIL ]		= gl_fbo_attach::STENCIL;
	}

/*
=================================================
	создает кадровый буфер
=================================================
*/
	bool GLFrameBuffer::Create()
	{
		if ( _uID != 0 )
		{
			DetachAll();
			return true;
		}

		CHECK_ERR( GLOM()->GenFramebuffers( 1, &_uID ) );
		return true;
	}

/*
=================================================
	проверяет статус буфера
=================================================
*/
	bool GLFrameBuffer::CheckStatus()
	{
		GLenum  status;
		GL_CALL( status = glCheckNamedFramebufferStatus( _uID, Target() ) );
		
		switch ( status )
		{
			case GL_FRAMEBUFFER_COMPLETE :
				return true;

			case GL_FRAMEBUFFER_UNDEFINED :
				RETURN_ERR( "Framebuffer: undefined" );

			case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT :
				RETURN_ERR( "Framebuffer incomplete: Attachment is not complete" );

			case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT :
				RETURN_ERR( "Framebuffer incomplete: No image is attached to FBO" );

			case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER :
				RETURN_ERR( "Framebuffer incomplete: Draw buffer" );

			case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER :
				RETURN_ERR( "Framebuffer incomplete: Read buffer" );

			case GL_FRAMEBUFFER_UNSUPPORTED :
				RETURN_ERR( "Unsupported by FBO implementation" );

			case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE :
				RETURN_ERR( "Framebuffer incomplete: Multisample" );

			case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS :
				RETURN_ERR( "Framebuffer incomplete: Layer targets" );
		};

		RETURN_ERR( "Framebuffer: Unknow error, code: 0x" << string().FormatI( status, 16 ) );
	}

/*
=================================================
	отсоединяет все присоединенные цели рендеринга
=================================================
*/
	void GLFrameBuffer::DetachAll()
	{
		FOR( i, _aBuffers )
		{
			if ( _aBuffers[i].eTarget == gl_texture::_UNKNOWN or _aBuffers[i].uID == 0 )
				continue;

			GL_CALL( glNamedFramebufferTexture2D( _uID, _aBuffers[i].eAttach, gl_texture::TEXTURE_2D, 0, 0 ) );

			_aBuffers[i].uID		= 0;
			_aBuffers[i].eTarget	= gl_texture::_UNKNOWN;
			_aDrawBuffers[i]		= gl_fbo_attach::NONE;
		}
		_aDrawBuffers[ _DEPTH ]			= gl_fbo_attach::DEPTH;
		_aDrawBuffers[ _DEPTH_STENCIL ]	= gl_fbo_attach::DEPTH_STENCIL;
		_aDrawBuffers[ _STENCIL ]		= gl_fbo_attach::STENCIL;
	}
	
/*
=================================================
	копирует данные из одного FBO в другой
=================================================
*/
	bool GLFrameBuffer::Blit(const GLFrameBuffer &pReadFBO, const irect &sRead, const irect &sWrite, gl_attrib_mask::type eMask, gl_fbo_blit::type eFilter) const
	{
		CHECK_ERR( pReadFBO.Id() != 0 );

		if ( (eMask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0 and eFilter == gl_fbo_blit::NEAREST )
		{
			WARNING( "filter changed to LINEAR, only LINEAR filter suported from depth or stencil format" );
			eFilter = gl_fbo_blit::LINEAR;
		}
		
		SMAN()->PushReadFramebuffer();
		SMAN()->PushDrawFramebuffer();

		SMAN()->BindReadFramebuffer( pReadFBO.Id() );
		SMAN()->BindDrawFramebuffer( _uID );

		GL_CALL( glBlitFramebuffer( sRead.left,		sRead.bottom,	sRead.right,	sRead.top,
									sWrite.left,	sWrite.bottom,	sWrite.right,	sWrite.top,
									eMask,			eFilter ) );

		SMAN()->PopReadFramebuffer();
		SMAN()->PopDrawFramebuffer();
		return true;
	}
	
/*
=================================================
	возвращает информацию о цели рендеринга
=================================================
*/
	bool GLFrameBuffer::GetAttachmentInfo(GLFrameBufferInfo &sInfo, gl_fbo_attach::type eAttach) const
	{
		bool	res = true;
		GLint	temp = 0;
		
		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( _uID, eAttach, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,		&temp ), res );
		sInfo.eObjType = (gl_fbo_object::type) temp;

		if ( sInfo.eObjType == gl_fbo_object::NONE )
			return res;
		
		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( _uID, eAttach, GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE,		(GLint *)& sInfo.aCompSize[GLFrameBufferInfo::R] ), res );
		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( _uID, eAttach, GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE,	(GLint *)& sInfo.aCompSize[GLFrameBufferInfo::G] ), res );
		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( _uID, eAttach, GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE,		(GLint *)& sInfo.aCompSize[GLFrameBufferInfo::B] ), res );
		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( _uID, eAttach, GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE,	(GLint *)& sInfo.aCompSize[GLFrameBufferInfo::A] ), res );
		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( _uID, eAttach, GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE,	(GLint *)& sInfo.aCompSize[GLFrameBufferInfo::DEPTH] ), res );
		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( _uID, eAttach, GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE,	(GLint *)& sInfo.aCompSize[GLFrameBufferInfo::STENCIL] ), res );
		
		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( _uID, eAttach, GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE,	&temp ), res );
		sInfo.eCompType = (gl_tex_component::type) temp;

		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( _uID, eAttach, GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING,	&temp ), res );
		sInfo.eColorEncoding = (gl_color_encoding::type) temp;

		return res;
	}
	
/*
=================================================
	записывает информацию о всех активных
	целях рендеринга
=================================================
*/
	bool GLFrameBuffer::GetEnabledAttachmentInfo(fb_info_array_t &aInfo) const
	{
		aInfo.Reserve( _BUFFER_SIZE );
		bool	ret = true;

		FOR( i, _aBuffers )
		{
			if ( _aBuffers[i].uID != 0 and
				 _aDrawBuffers[i] != gl_fbo_attach::NONE )
			{
				aInfo.PushBack( GLFrameBufferInfo() );
				
				if ( !GetAttachmentInfo( aInfo.Back(), _aDrawBuffers[i] ) )
				{
					//aInfo.PopBack();
					ret = false;
				}
			}
		}

		return ret;
	}

/*
=================================================
	записывает информацию о всех присоединенных
	целях рендеринга
=================================================
*/
	bool GLFrameBuffer::GetAllAttachmentInfo(fb_info_array_t &aInfo) const
	{
		aInfo.Reserve( _BUFFER_SIZE );
		bool	ret = true;

		FOR( i, _aBuffers )
		{
			if ( _aBuffers[i].uID != 0 )
			{
				aInfo.PushBack( GLFrameBufferInfo() );
				
				if ( !GetAttachmentInfo( aInfo.Back(), _aDrawBuffers[i] ) )
				{
					//aInfo.PopBack();
					ret = false;
				}
			}
		}

		return ret;
	}

/*
=================================================
	
=================================================
*/
	bool GLFrameBuffer::s_CheckFrameBufferFunctions()
	{
		return true;
	}
	


//----------------------- GLDefaultFrameBuffer --------------------//
/*
=================================================
	возвращает размер буфера необходимый для
	записи в него изображения с экрана
=================================================
*/
	bool GLDefaultFrameBuffer::GetImageDataSize(const uvec2 &vDim, gl_pixel_format::type eFormat, gl_pixel::type eType, usize &uDataSize)
	{
		uint	u_bpp = 0;
		
		if ( !GLTexFormatInfo::GetBitPerPixel( eFormat, eType, u_bpp ) )
			return false;

		uDataSize = u_bpp * vDim.x * vDim.y;
		return true;
	}
	
/*
=================================================
	записывает информацию о системном буфере
=================================================
*/
	bool GLDefaultFrameBuffer::GetBufferInfo(GLFrameBufferInfo &sInfo, gl_system_buffer::type eBuffer)
	{
		bool	res = true;
		GLint	temp = 0;
		
		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( 0, eBuffer, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,		&temp ), res );
		sInfo.eObjType = (gl_fbo_object::type) temp;

		if ( sInfo.eObjType != gl_fbo_object::DEFAULT_FB )
			return res;
		
		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( 0, eBuffer, GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE,		(GLint *)& sInfo.aCompSize[GLFrameBufferInfo::R] ), res );
		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( 0, eBuffer, GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE,	(GLint *)& sInfo.aCompSize[GLFrameBufferInfo::G] ), res );
		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( 0, eBuffer, GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE,		(GLint *)& sInfo.aCompSize[GLFrameBufferInfo::B] ), res );
		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( 0, eBuffer, GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE,	(GLint *)& sInfo.aCompSize[GLFrameBufferInfo::A] ), res );
		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( 0, eBuffer, GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE,	(GLint *)& sInfo.aCompSize[GLFrameBufferInfo::DEPTH] ), res );
		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( 0, eBuffer, GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE,	(GLint *)& sInfo.aCompSize[GLFrameBufferInfo::STENCIL] ), res );
		
		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( 0, eBuffer, GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE,	&temp ), res );
		sInfo.eCompType = (gl_tex_component::type) temp;

		GL_CALL_BA( glGetNamedFramebufferAttachmentParameteriv( 0, eBuffer, GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING,	&temp ), res );
		sInfo.eColorEncoding = (gl_color_encoding::type) temp;

		return res;
	}
	
/*
=================================================
	записывает информацию о всех системных буферах
=================================================
*/
	bool GLDefaultFrameBuffer::GetAllBuffersInfo(fb_info_array_t &aInfo)
	{
		static const gl_system_buffer::type	s_aBuffers[] = { gl_system_buffer::DEPTH, gl_system_buffer::STENCIL,
															 gl_system_buffer::BACK_LEFT, gl_system_buffer::BACK_RIGHT,
															 gl_system_buffer::FRONT_LEFT, gl_system_buffer::FRONT_RIGHT };

		bool	res = true;

		aInfo.Reserve( CountOf( s_aBuffers ) );

		for (usize i = 0; i < CountOf( s_aBuffers ); ++i)
		{
			aInfo.PushBack( GLFrameBufferInfo() );

			if ( !GetBufferInfo( aInfo.Back(), s_aBuffers[i] ) )
			{
				//aInfo.PopBack();
				res = false;
			}
		}

		return res;
	}


//-------------------------------------------------------------------
	
}	// GLShell
}	// UX_STL